/* -------------------------------------------------------------------------
 *
 * contrib/sepgsql/proc.c
 *
 * 与过程对象对应的例程
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/seclabel.h"
#include "lib/stringinfo.h"
#include "sepgsql.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"

/*
 * sepgsql_proc_post_create
 *
 * 此例程为新定义的过程分配默认安全标签。
 */
void sepgsql_proc_post_create(Oid fc_functionId)
{
	Relation	fc_rel;
	ScanKeyData fc_skey;
	SysScanDesc fc_sscan;
	HeapTuple	fc_tuple;
	char	   *fc_nsp_name;
	char	   *fc_scontext;
	char	   *fc_tcontext;
	char	   *fc_ncontext;
	uint32		fc_required;
	int			fc_i;
	StringInfoData fc_audit_name;
	ObjectAddress fc_object;
	Form_pg_proc fc_proForm;

	/*
	 * 获取新过程的命名空间。因为 pg_proc 条目现在不可见，我们需要使用 SnapshotSelf 来扫描目录。
	 */
	fc_rel = table_open(ProcedureRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey,
				Anum_pg_proc_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_functionId));

	fc_sscan = systable_beginscan(fc_rel, ProcedureOidIndexId, true,
							   SnapshotSelf, 1, &fc_skey);

	fc_tuple = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for function %u", fc_functionId);

	fc_proForm = (Form_pg_proc) GETSTRUCT(fc_tuple);

	/*
	 * 检查命名空间的 db_schema:{add_name} 权限
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_proForm->pronamespace;
	fc_object.objectSubId = 0;
	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_SCHEMA,
							SEPG_DB_SCHEMA__ADD_NAME,
							getObjectIdentity(&fc_object, false),
							true);

	/*
	 * XXX - db_language:{implement} 也应该在这里检查
	 */


	/*
	 * 在指定命名空间下创建新过程对象时计算默认安全标签。
	 */
	fc_scontext = sepgsql_get_client_label();
	fc_tcontext = sepgsql_get_label(NamespaceRelationId,
								 fc_proForm->pronamespace, 0);
	fc_ncontext = sepgsql_compute_create(fc_scontext, fc_tcontext,
									  SEPG_CLASS_DB_PROCEDURE,
									  NameStr(fc_proForm->proname));

	/*
	 * 检查 db_procedure:{create (install)} 权限
	 */
	initStringInfo(&fc_audit_name);
	fc_nsp_name = get_namespace_name(fc_proForm->pronamespace);
	appendStringInfo(&fc_audit_name, "%s(",
					 quote_qualified_identifier(fc_nsp_name, NameStr(fc_proForm->proname)));
	for (fc_i = 0; fc_i < fc_proForm->pronargs; fc_i++)
	{
		if (fc_i > 0)
			appendStringInfoChar(&fc_audit_name, ',');

		fc_object.classId = TypeRelationId;
		fc_object.objectId = fc_proForm->proargtypes.values[fc_i];
		fc_object.objectSubId = 0;
		appendStringInfoString(&fc_audit_name, getObjectIdentity(&fc_object, false));
	}
	appendStringInfoChar(&fc_audit_name, ')');

	fc_required = SEPG_DB_PROCEDURE__CREATE;
	if (fc_proForm->proleakproof)
		fc_required |= SEPG_DB_PROCEDURE__INSTALL;

	sepgsql_avc_check_perms_label(fc_ncontext,
								  SEPG_CLASS_DB_PROCEDURE,
								  fc_required,
								  fc_audit_name.data,
								  true);

	/*
	 * 为新过程分配默认安全标签
	 */
	fc_object.classId = ProcedureRelationId;
	fc_object.objectId = fc_functionId;
	fc_object.objectSubId = 0;
	SetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG, fc_ncontext);

	/*
	 * 清理
	 */
	systable_endscan(fc_sscan);
	table_close(fc_rel, AccessShareLock);

	pfree(fc_audit_name.data);
	pfree(fc_tcontext);
	pfree(fc_ncontext);
}

/*
 * sepgsql_proc_drop
 *
 * 它检查删除提供的函数的权限。
 */
void sepgsql_proc_drop(Oid fc_functionId)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;

	/*
	 * 检查 db_schema:{remove_name} 权限
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = get_func_namespace(fc_functionId);
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_SCHEMA,
							SEPG_DB_SCHEMA__REMOVE_NAME,
							fc_audit_name,
							true);
	pfree(fc_audit_name);

	/*
	 * 检查 db_procedure:{drop} 权限
	 */
	fc_object.classId = ProcedureRelationId;
	fc_object.objectId = fc_functionId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_PROCEDURE,
							SEPG_DB_PROCEDURE__DROP,
							fc_audit_name,
							true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_proc_relabel
 *
 * 它检查通过 `seclabel' 重新标记提供函数的权限。
 */
void sepgsql_proc_relabel(Oid fc_functionId, const char *fc_seclabel)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;

	fc_object.classId = ProcedureRelationId;
	fc_object.objectId = fc_functionId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	/*
	 * 检查 db_procedure:{setattr relabelfrom} 权限
	 */
	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_PROCEDURE,
							SEPG_DB_PROCEDURE__SETATTR |
							SEPG_DB_PROCEDURE__RELABELFROM,
							fc_audit_name,
							true);

	/*
	 * 检查 db_procedure:{relabelto} 权限
	 */
	sepgsql_avc_check_perms_label(fc_seclabel,
								  SEPG_CLASS_DB_PROCEDURE,
								  SEPG_DB_PROCEDURE__RELABELTO,
								  fc_audit_name,
								  true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_proc_setattr
 *
 * 它检查修改提供函数的权限。
 */
void sepgsql_proc_setattr(Oid fc_functionId)
{
	Relation	fc_rel;
	ScanKeyData fc_skey;
	SysScanDesc fc_sscan;
	HeapTuple	fc_oldtup;
	HeapTuple	fc_newtup;
	Form_pg_proc fc_oldform;
	Form_pg_proc fc_newform;
	uint32		fc_required;
	ObjectAddress fc_object;
	char	   *fc_audit_name;

	/*
	 * 获取更新的目录
	 */
	fc_rel = table_open(ProcedureRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey,
				Anum_pg_proc_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_functionId));

	fc_sscan = systable_beginscan(fc_rel, ProcedureOidIndexId, true,
							   SnapshotSelf, 1, &fc_skey);
	fc_newtup = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_newtup))
		elog(ERROR, "could not find tuple for function %u", fc_functionId);
	fc_newform = (Form_pg_proc) GETSTRUCT(fc_newtup);

	/*
	 * 获取旧的目录
	 */
	fc_oldtup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_functionId));
	if (!HeapTupleIsValid(fc_oldtup))
		elog(ERROR, "cache lookup failed for function %u", fc_functionId);
	fc_oldform = (Form_pg_proc) GETSTRUCT(fc_oldtup);

	/*
	 * 此 ALTER 命令是否会对命名空间进行操作？
	 */
	if (fc_newform->pronamespace != fc_oldform->pronamespace)
	{
		sepgsql_schema_remove_name(fc_oldform->pronamespace);
		sepgsql_schema_add_name(fc_oldform->pronamespace);
	}
	if (strcmp(NameStr(fc_newform->proname), NameStr(fc_oldform->proname)) != 0)
		sepgsql_schema_rename(fc_oldform->pronamespace);

	/*
	 * 检查 db_procedure:{setattr (install)} 权限
	 */
	fc_required = SEPG_DB_PROCEDURE__SETATTR;
	if (!fc_oldform->proleakproof && fc_newform->proleakproof)
		fc_required |= SEPG_DB_PROCEDURE__INSTALL;

	fc_object.classId = ProcedureRelationId;
	fc_object.objectId = fc_functionId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_PROCEDURE,
							fc_required,
							fc_audit_name,
							true);
	/* 清理 */
	pfree(fc_audit_name);

	ReleaseSysCache(fc_oldtup);
	systable_endscan(fc_sscan);
	table_close(fc_rel, AccessShareLock);
}

/*
 * sepgsql_proc_execute
 *
 * 它检查执行提供函数的权限
 */
void sepgsql_proc_execute(Oid fc_functionId)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;

	/*
	 * 检查 db_procedure:{execute} 权限
	 */
	fc_object.classId = ProcedureRelationId;
	fc_object.objectId = fc_functionId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);
	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_PROCEDURE,
							SEPG_DB_PROCEDURE__EXECUTE,
							fc_audit_name,
							true);
	pfree(fc_audit_name);
}
